////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//                   PEER 2 PEER FILE SHARING SYSTEM                          //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
package com.cse.p2p.common;

import java.util.Arrays;

/**
 * This class defines a message, which contains what computers want to say to 
 * others. To identify what the message says, look at its <code>type</code>
 * property, all available message types is defined as static constances of the
 * {@link MsgType} class.
 * 
 * @author TrungHieu
 */
public class Message {

	private String command;
	private String keyword;
	private String data;
	private int type;

	/**
	 * Create a <i>Message</i> from a command, a keyword and the data string
	 * 
	 * @param command String The command of the message 
	 * @param keyword String The keyword of the message
	 * @param data String The data of the message
	 */
	public Message( String command, String keyword, String data ) {
		this.command = command;
		this.keyword = keyword;
		this.data = data;

		// message analyzing
		if ( this.command.equals( Command.GET ) ) {
			if ( this.keyword.equals( Keyword.LIST ) ) {
				this.type = MsgType.REQ_FOR_AVAI_PIECES;
			}
			else if ( this.keyword.equals( Keyword.PIECE ) ) {
				this.type = MsgType.REQ_ASK_A_PIECE;
			}
			else if ( this.keyword.equals( Keyword.VERIFY ) ) {
				this.type = MsgType.REQ_VRF_ALL_PIECES;
			}
			else if ( this.keyword.equals( Keyword.UP ) ) {
				if ( this.data.equals( "DIC" ) ) {
					this.type = MsgType.REQ_UP_DIC_FILE;
				}
				else {
					this.type = MsgType.REQ_UP_HOST_FILE;
				}
			}
			else if ( this.keyword.equals( Keyword.SIGNIN ) || this.keyword.equals( Keyword.SIGNOUT ) ) {
				this.type = MsgType.REQ_REG_NICK;
			}
			else {
				this.type = MsgType.UNKNOWN_TYPE;
			}
		}
		else if ( this.command.equals( Command.PUT ) ) {
			if ( this.keyword.equals( Keyword.LIST ) ) {
				this.type = MsgType.RES_FOR_AVAI_PIECES;
			}
			else if ( this.keyword.equals( Keyword.PIECE ) ) {
				this.type = MsgType.RES_ASK_A_PIECE;
			}
			else if ( this.keyword.equals( Keyword.VERIFY ) ) {
				this.type = MsgType.RES_VRF_ALL_PIECES;
			}
			else if ( this.keyword.equals( Keyword.UP ) ) {
				this.type = MsgType.RES_UP_DIC_FILE;
			}
			else if ( this.keyword.equals( Keyword.OK ) ) {
				this.type = MsgType.RES_FILE_TRANSFER;
			}
			else if ( this.keyword.equals( Keyword.SIGN ) ) {
				this.type = MsgType.RES_REG_NICK;
			}
			else {
				this.type = MsgType.UNKNOWN_TYPE;
			}
		}
		else {
			this.type = MsgType.UNKNOWN_TYPE;
		}
	}

	/**
	 * Create a <i>Message</i> with no data segment from a command and a keyword
	 * 
	 * @param command String The command of the message 
	 * @param keyword String The keyword of the message
	 */
	public Message( String command, String keyword ) {
		this.command = command;
		this.keyword = keyword;
		this.data = "";
	}

	/**
	 * Get the command of this message.
	 * 
	 * @return The command string.
	 */
	public String getCommand() {
		return command;
	}

	/**
	 * Get the data of this message.
	 * 
	 * @return The data string.
	 */
	public String getData() {
		return data;
	}

	/**
	 * Get the keyword of this message.
	 * 
	 * @return The keyword string
	 */
	public String getKeyword() {
		return keyword;
	}

	public int getType() {
		return type;
	}

	/**
	 * Check whether a message is valid. The command and the keyword must be one
	 * of the ones defines in the <code>Command</code> and <code>Keyword</code>
	 * static class, the message is optional.
	 *  
	 * @return whether this message is valid.
	 */
	public boolean isValid() {
		return Command.isValid( this.command ) && Keyword.isValid( this.keyword );
	}

	@Override
	public String toString() {
		return command + " " + keyword + ( data.isEmpty() ? "" : " " + data );
	}

	/**
	 * Create a {@link Message} from a string. The function split the input
	 * string in to three or less segment. The first word is the 
	 * {@link Command}, the second one is the {@link Keyword} and the rest of 
	 * the strign is the data. A valid message must contains at least a 
	 * <code>command</code> and a <code>keyword</code>, the data part is 
	 * optional.
	 * 
	 * @param	s	The string has to be converted
	 * @return	a <code>Message</code> or <code>null</code> if the string is 
	 *			invalid
	 */
	public static Message fromString( String s ) {
		String[] mesSegments = s.split( " ", 3 );
		if ( mesSegments.length == 3 && Command.isValid( mesSegments[0] ) && Keyword.isValid( mesSegments[1] ) ) {
			return new Message( mesSegments[0], mesSegments[1], mesSegments[2] );
		}
		else if ( mesSegments.length == 2 && Command.isValid( mesSegments[0] ) && Keyword.isValid( mesSegments[1] ) ) {
			return new Message( mesSegments[0], mesSegments[1] );
		}
		else {
			return null;
		}
	}

	/**
	 * This abstract static class defines the command constants.
	 * 
	 * @author TrungHieu
	 */
	public static abstract class Command {

		public static final String GET = "GET";
		public static final String PUT = "PUT";

		/**
		 * The list of all available commands. 
		 * 
		 * @return The list of all available commands
		 */
		public static String[] availableCommands() {
			String[] commands = { GET, PUT };
			return commands;
		}

		/**
		 * Check whether a command is valid (it is one of available commands).
		 * 
		 * @param command The command need to be checked.
		 * @return Is the <code>command</code> a valid command.
		 */
		public static boolean isValid( String command ) {
			return Arrays.asList( availableCommands() ).contains( command );
		}
	}

	/**
	 * This abstract static class defines the keyword constants.
	 * 
	 * @author TrungHieu
	 */
	public static abstract class Keyword {

		public static final String OK = "OK";
		public static final String NONE = "NONE";
		public static final String LIST = "LIST";
		public static final String VERIFY = "VERIFY";
		public static final String PIECE = "PIECE";
		public static final String UP = "UP";
		public static final String SIGNIN = "SIGNIN";
		public static final String SIGNOUT = "SIGNOUT";
		public static final String SIGN = "SIGN";

		/**
		 * The list of all available keywords. 
		 * 
		 * @return The list of all available keywords
		 */
		public static String[] availableKeywords() {
			String[] keywords = { LIST, NONE, OK, PIECE, UP, VERIFY, SIGNIN, SIGNOUT, SIGN };
			return keywords;
		}

		/**
		 * Check whether a keyword is valid (it is one of available keywords).
		 * 
		 * @param keyword The keyword need to be checked.
		 * @return Is the <code>keyword</code> a valid command.
		 */
		public static boolean isValid( String keyword ) {
			return Arrays.asList( availableKeywords() ).contains( keyword );
		}
	}

	/**
	 * This abstract class defines available message types
	 */
	public static abstract class MsgType {

		/**
		 * Client -> Server: get the list of all available pieces of a file
		 */
		public static final int REQ_FOR_AVAI_PIECES = 0;
		/**
		 * Server -> Client: the info of all available pieces of a file
		 */
		public static final int RES_FOR_AVAI_PIECES = 1;
		/**
		 * Server -> one Peer: the info of all available pieces of a file
		 */
		public static final int REQ_VRF_ALL_PIECES = 2;
		/**
		 * one Peer -> Server: the info of all available pieces of a file
		 */
		public static final int RES_VRF_ALL_PIECES = 3;
		/**
		 * Client -> one Peer:  ask for one piece of a file
		 */
		public static final int REQ_ASK_A_PIECE = 4;
		/**
		 * one Peer -> Client: reponse for the request of the client
		 */
		public static final int RES_ASK_A_PIECE = 5;
		/**
		 * Client -> Server: ask for the permision of uploading a file
		 */
		public static final int REQ_UP_HOST_FILE = 6;
		/**
		 * Server -> Client: request for the dicfile transfering
		 */
		public static final int REQ_UP_DIC_FILE = 7;
		/**
		 * Client -> Server : request for the dicfile transfering
		 */
		public static final int RES_UP_DIC_FILE = 8;
		/**
		 * Response for transfering request
		 */
		public static final int RES_FILE_TRANSFER = 9;
		/**
		 * Unknown message type
		 */
		public static final int REQ_REG_NICK = 10;
		public static final int RES_REG_NICK = 11;
		public static final int UNKNOWN_TYPE = -1;
	}
}
